#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <stdio.h>

#include "driver_pmu.h"
#include "driver_system.h"
#include "driver_spi.h"

#include "co_printf.h"

#define SPI_MCLK_L          48000000
#define SPI_MCLK_H          96000000

__attribute__((section("ram_code"))) void spi_enable(uint32_t spi_addr)
{
    volatile struct spi_reg_t * const spi = (volatile struct spi_reg_t *)spi_addr;
    spi->spi_ssienr.ssi_en = 1;
}

__attribute__((section("ram_code"))) void spi_disable(uint32_t spi_addr)
{
    volatile struct spi_reg_t * const spi = (volatile struct spi_reg_t *)spi_addr;
    spi_wait_busy_bit(spi_addr); 
    spi->spi_ssienr.ssi_en = 0;
}

__attribute__((section("ram_code"))) void spi_wait_busy_bit(uint32_t spi_addr)
{
    while(spi_fifo_is_busy(spi_addr) == 1);
}

__attribute__((section("ram_code"))) bool spi_fifo_is_busy(uint32_t spi_addr)
{
    volatile struct spi_reg_t * const spi = (volatile struct spi_reg_t *)spi_addr;
    return spi->spi_sr.busy;
}

__attribute__((section("ram_code"))) bool spi_rx_fifo_is_full(uint32_t spi_addr)
{
    volatile struct spi_reg_t * const spi = (volatile struct spi_reg_t *)spi_addr;
    return spi->spi_sr.rff;
}

__attribute__((section("ram_code"))) bool spi_tx_fifo_is_empty(uint32_t spi_addr)
{
    volatile struct spi_reg_t * const spi = (volatile struct spi_reg_t *)spi_addr;
    return spi->spi_sr.tfe;
}

__attribute__((section("ram_code"))) bool spi_tx_fifo_is_not_full(uint32_t spi_addr)
{
    volatile struct spi_reg_t * const spi = (volatile struct spi_reg_t *)spi_addr;
    return spi->spi_sr.tfnf;
}

__attribute__((section("ram_code"))) bool spi_rx_fifo_is_not_empty(uint32_t spi_addr)
{
    volatile struct spi_reg_t * const spi = (volatile struct spi_reg_t *)spi_addr;
    return spi->spi_sr.rfne;
}

__attribute__((section("ram_code"))) void spi_clean_fifo(uint32_t spi_addr)
{
    volatile uint8_t data;
    volatile struct spi_reg_t * const spi = (volatile struct spi_reg_t *)spi_addr;
    while(spi_rx_fifo_is_not_empty(spi_addr))
    {
        data = spi->spi_dr[0];
    }
}

__attribute__((section("ram_code"))) void spi_send_byte(uint32_t spi_addr, const uint32_t c)
{
    volatile struct spi_reg_t * const spi = (volatile struct spi_reg_t *)spi_addr;
    while(!spi_tx_fifo_is_not_full(spi_addr));
    spi->spi_dr[0] = c;
}

__attribute__((section("ram_code"))) uint32_t spi_send_and_recv(uint32_t spi_addr, uint32_t c )
{
    volatile struct spi_reg_t * const spi = (volatile struct spi_reg_t *)spi_addr;
    while(!spi_tx_fifo_is_not_full(spi_addr));
    spi->spi_dr[0] = c;
    spi_wait_busy_bit(spi_addr);
    while(!spi_rx_fifo_is_not_empty(spi_addr));
    return spi->spi_dr[0];
}

__attribute__((section("ram_code"))) void spi_send_data(uint32_t spi_addr, uint8_t* data, uint32_t len)
{
    volatile struct spi_reg_t * const spi = (volatile struct spi_reg_t *)spi_addr;
    while(len --)
    {
       while(!spi_tx_fifo_is_not_full(spi_addr));
        spi->spi_dr[0] = *data++; 
    }
}

__attribute__((section("ram_code"))) void spi_send_data_16bits(uint32_t spi_addr, uint16_t* data, uint32_t len)
{
    volatile struct spi_reg_t * const spi = (volatile struct spi_reg_t *)spi_addr;
    while(len --)
    {
        while(!spi_tx_fifo_is_not_full(spi_addr));
        spi->spi_dr[0] = *data++; 
    }
}

__attribute__((section("ram_code"))) uint16_t spi_fill_fifo(uint32_t spi_addr, uint8_t *buffer, uint16_t len)
{
    volatile struct spi_reg_t * const spi = (volatile struct spi_reg_t *)spi_addr;
    uint16_t send_length = 0;
    for(uint16_t i = 0 ; i < len;i++)
    {
        if(spi->spi_sr.tfnf)
        {
            spi->spi_dr[0] = *buffer++;
            send_length++;
        }
        else
        {
            break;
        }
    }
    return send_length;
}

__attribute__((section("ram_code"))) uint16_t spi_get_data(uint32_t spi_addr,uint8_t* data,uint16_t len)
{
    uint16_t actual_value = 0;
    uint32_t count = 0;
    volatile struct spi_reg_t * const spi = (volatile struct spi_reg_t *)spi_addr;
    while(len --)
    {
        while(!spi_rx_fifo_is_not_empty(spi_addr))
        {
            if(count>1000)
                break;
            
            count++;
        }
        *data++ = spi->spi_dr[0];
        actual_value++;
    }
    return actual_value;
}

void spi_init(struct spi_config_t *config)
{
    volatile struct spi_reg_t * const spi = (volatile struct spi_reg_t *)config->spi_address;
    
    switch(config->spi_address) {
        case SPIM0:
           system_regs->clk_cfg.ssim0_sclk_sel = config->source_spi_clk;
           system_regs->clk_gate.ssim0_clk_en = 1;
           system_regs->misc.ssim0_apb_clk_en = 1; 
            break;
        case SPIM1:
           system_regs->clk_cfg.ssim1_sclk_sel = config->source_spi_clk;
           system_regs->clk_gate.ssim1_clk_en = 1;
           system_regs->misc.ssim1_apb_clk_en = 1; 
            break;
        case SPIS0:
            system_regs->clk_cfg.ssis_sclk_sel = config->source_spi_clk;
            system_regs->clk_gate.ssis_clk_en = 1;
            system_regs->misc.ssis_apb_clk_en = 1; 
            break;
        default:
            return;
    }
    
    spi_disable(config->spi_address);
    spi->spi_ctr0.frf = config->spi_frame_format;
    spi->spi_ctr0.dfs = config->spi_frame_size;
    spi->spi_ctr0.spi_frf = config->data_format; 
    spi->spi_ctr0.scph = config->spi_spch;
    spi->spi_ctr0.scpol = config->spi_scpol;
    spi->spi_ctr0.tmod = config->spi_transfer_mode;
    spi->spi_ctr0.sste = config->slave_select_toggle;
    
    if(config->source_spi_clk == SPI_SOURCE_CLK_96MHZ) {
        spi->spi_baudr.sckdiv = SPI_MCLK_H / config->baud_rate;
    }
    else {
        spi->spi_baudr.sckdiv = SPI_MCLK_L / config->baud_rate;
    }
    spi->spi_ser.SER = 1;

    spi->spi_spi_ctrlr0.trans_type = config->trans_type;
    spi->spi_spi_ctrlr0.addr_l = config->spi_addrL;
    spi->spi_spi_ctrlr0.inst_l = config->spi_instL;
    spi->spi_spi_ctrlr0.wait_cycles = 8;
    
    spi_clean_fifo(config->spi_address);

    spi_enable(config->spi_address);
    
}

__attribute__((section("ram_code"))) void spi_set_fifo_level(uint32_t spi_addr,uint8_t tft,uint8_t rft)
{
    volatile struct spi_reg_t * const spi = (volatile struct spi_reg_t *)spi_addr;
    spi_disable(spi_addr);
    spi->spi_txftlr.tft = tft;
    spi->spi_rxftlr.rft = rft;
    spi_enable(spi_addr);
}

__attribute__((section("ram_code"))) uint8_t spi_clear_isr(uint32_t spi_addr)
{

    volatile struct spi_reg_t * const spi = (volatile struct spi_reg_t *)spi_addr;
    return spi->spi_icr.icr;

}

__attribute__((section("ram_code"))) void spi_set_int(uint32_t spi_addr, uint8_t int_type)
{
    volatile struct spi_reg_t * const spi = (volatile struct spi_reg_t *)spi_addr;
    spi_disable(spi_addr);
    spi->spi_imr = int_type;
    spi_enable(spi_addr);
}

__attribute__((section("ram_code"))) uint32_t spi_get_isr_status(uint32_t spi_addr)
{
    volatile struct spi_reg_t * const spi = (volatile struct spi_reg_t *)spi_addr;
    return *(volatile uint32_t *)&spi->spi_isr;
}

__attribute__((section("ram_code")))void spi_multiline_read(uint32_t spi_addr,uint8_t opcode,uint32_t offset,uint8_t* data ,uint8_t length)
{
    volatile struct spi_reg_t * const spi = (volatile struct spi_reg_t *)spi_addr;
  
     uint8_t i;
    
     spi_disable(spi_addr);
     spi->spi_ctr1.ndf = length-1;
     spi_enable(spi_addr);
     spi->spi_dr[0] = opcode;
     spi->spi_dr[0] = offset>>8;
     spi->spi_dr[0] = offset;
     
     spi_wait_busy_bit(spi_addr);
    
     for(i = 0 ; i < length; i++)
    {
        while(!spi_rx_fifo_is_not_empty(spi_addr));
        
        *data++ = spi->spi_dr[0];
    }
    
}

#if 0
__attribute__((weak)) __attribute__((section("ram_code"))) void spim0_isr(void)
{
    uint8_t status;
    status = spi_get_isr_status(SPIM0);
    co_printf("status:%02x\r\n",status);
    if(status == SPI_INT_TXEIS)
    {
        
    }
    else if(status == SPI_INT_TXOIS)
    {
        
    }
    else if(status == SPI_INT_RXUIS)
    {
            
    }
    else if(status == SPI_INT_RXOIS)
    {
       
    }
    else if(status == SPI_INT_RXFIS)
    {
       
    }
    else if(status&SPI_INT_MSTIS)
    {
        
    }
    spi_clear_isr(SPIM0);
}

__attribute__((weak)) __attribute__((section("ram_code"))) void spim1_isr(void)
{
    uint8_t status;
    status = spi_get_isr_status(SPIM1);
    co_printf("status:%02x\r\n",status);
    if(status == SPI_INT_TXEIS)
    {
        
    }
    else if(status == SPI_INT_TXOIS)
    {
        
    }
    else if(status == SPI_INT_RXUIS)
    {
            
    }
    else if(status == SPI_INT_RXOIS)
    {
       
    }
    else if(status == SPI_INT_RXFIS)
    {
       
    }
    else if(status&SPI_INT_MSTIS)
    {
        
    }
    spi_clear_isr(SPIM1);
}
#endif

#if 0
static __attribute__((section("ram_code")))  void test_spi_write_flash(uint8_t opcode, uint32_t address, uint8_t *data, uint32_t length)
{
    spi_send_data(SPIM0, &opcode, 1);
    spi_send_data(SPIM0, (void *)&address, 3);
    spi_send_data(SPIM0, data, length);
}

void test_spi(void)
{
    struct spi_config_t spi_config;

    co_printf("test spi\r\n");

    /* initial io mux for spi */
    system_set_port_pull(GPIO_PA2|GPIO_PA3|GPIO_PA4|GPIO_PA5|GPIO_PA6|GPIO_PA7, GPIO_PULL_UP, true);
    system_set_port_mux(GPIO_PORT_A, GPIO_BIT_2, PORTA2_FUNC_SSIM0_IO0);
    system_set_port_mux(GPIO_PORT_A, GPIO_BIT_3, PORTA3_FUNC_SSIM0_IO1);
    system_set_port_mux(GPIO_PORT_A, GPIO_BIT_4, PORTA4_FUNC_SSIM0_IO2);
    system_set_port_mux(GPIO_PORT_A, GPIO_BIT_5, PORTA5_FUNC_SSIM0_IO3);
    system_set_port_mux(GPIO_PORT_A, GPIO_BIT_6, PORTA6_FUNC_SSIM0_CLK);
    system_set_port_mux(GPIO_PORT_A, GPIO_BIT_7, PORTA7_FUNC_SSIM0_CSN);

    /* initial spi controller */
    spi_config.spi_address = SPIM0;
    spi_config.source_spi_clk = SPI_SOURCE_CLK_96MHZ;
    spi_config.spi_frame_format = SPI_FRAME_FORMAT_MOTOROLA;
    spi_config.spi_frame_size = SPI_FRAME_SIZE_8BITS;
    spi_config.data_format = SPI_DATA_FRAME_FORMAT_STD;
    spi_config.spi_transfer_mode = SPI_TRANSFER_MODE_TX_ONLY;
    spi_config.spi_spch = 1;
    spi_config.spi_scpol = 1;
    spi_config.slave_select_toggle = false;
    spi_config.trans_type = SPI_TRANS_TYPE_INST_STAND_ADDR_STAND;
    spi_config.spi_addrL = SPI_ADDRESS_LENGTH_24;
    spi_config.spi_instL =  SPI_INSTRUCTION_LENGTH_8;
    spi_config.baud_rate = 24000000;
    spi_init(&spi_config);

    test_spi_write_flash(0x03, 0x00201000, (void *)0x11000000, 1024);
}
#endif

